# -*- encoding: UTF-8 -*- 
# @Author  : K4I
# @Date    : 2023-11-22
# @Version : 0.1.2

# Modifyed @User1396529

""" 代码整体逻辑说明

汽车来到加油站后，需要一个服务台加油并且获得其所需要的油量。
如果服务台的燃料消耗光了，则汽车需要等待燃油车补充燃油。
假设开始时燃料储存为满值。

燃油单位：升

TODO(K4I):

未完成的任务如下：

1. 记录每辆车的等待时间，绘图，检查是否无限排队
2. 输出各项指标参数，从存储论和排队论两个角度考虑
3. 考虑将代码长篇大论额输出放入 log 而不是 print
"""

# In[0]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pylab import mpl
# 指定默认字体：解决plot不能显示中文问题
# 警告：该方法只能在 Windows 计算机上生效
# 如果使用 Linux 或者 Mac 电脑，请注释之
mpl.rcParams[
    'font.sans-serif'
    ] = ['SimHei'] 
# 解决保存图像是负号'-'显示为方块的问题
mpl.rcParams[
    'axes.unicode_minus'
    ] = False 
import seaborn as sns
import random
import simpy

# In[1]:
# 随机种子
RANDOM_SEED = 42                 # 一切时间、空间及生命的终极答案

# 加油站参数
FUEL_PUMP_CAPACITY = 6000        # 加油站的燃料储存容量/L
NUM_REFUEL_MACHINE = 4           # 加油机的数量
THRESHOLD = 10                   # 低于 10% 的阈值便需要对库存补充燃油

# 燃料车参数
FUEL_TANK_SIZE = 55              # 燃油车容量/L
TANK_TRUCK_TIME = 300            # 燃油车到达时间 /s
GAS_STATION_REFUELLING_SPEED = 4 # 加油车给加油站上油的速度

# 汽车（顾客）参数
FUEL_TANK_LEVEL = [5, 25]        # 汽车剩余汽油量范围 
REFUELLING_SPEED = 2             # 加油速度 L/s
INTERVAL = 100                   # 汽车到达时间的负指数分布参数 

# T_INTER = [30, 300]             
# 
# 30-300s随机生成一量汽车
# 
# 这个参数在修改之后已经没有用了，只是原始的代码里面给出了
# 下面的函数 car_arrive_time 是按照这个参数指定的范围设计的
# 这里保留在代码注释里面

SIM_TIME = 5e4                   # 模拟时间/s

CAR_MIN_PATIENCE = 120
CAT_MAX_PATIENCE = 600

# In[2]:
def car_arrive_time(interval):
    t = random.expovariate(1.0 / interval)
    # 时间符合负指数分布
    if t < 30:  # 下限  
        t = 30 
    elif t > 300:  # 上限  
        t = 300
    return t

# In[3]:
class GasStationSimulator:
    """ 加油站模拟器主类
        
        方法：

        - __init__ 初始化类
        - car_source 汽车源，定期生成汽车
        - car 汽车进程，进入加油站并申请加油
        - gas_station_control 加油站油量控制
        - tank_truck 加油车进程，为加油机加油 
    """

    def __init__(
            self, env,                   
            # 类自身及环境
            fuel_pump_capacity,          
            # 大油罐子的容量
            num_refuel_machine,          
            # 加油机的数量
            interval,                    
            # 汽车到达时间的负指数分布参数
            gas_station_refuelling_speed, 
            # 加油车给加油站上油的速度
            threshold,                   
            # 呼叫加油车的阈值
            car_min_patience,
            car_max_patience
            ):

        self.env = env

        self.wait_time_list =[ 
            [], # 汽车名
            [], # 汽车排队时间
            [], # 汽车是否离开
        ]
        self.gas_station_status_list = [
            [], # 时间戳
            [], # 该时间戳下加油机前的队伍的长度
            [], # 该时间戳下加油站大门前的队伍的长度
            []  # 该时间戳下加油站的总油量
        ]

        self.gas_station_refuelling_speed =\
             gas_station_refuelling_speed
        self.car_min_patience = car_min_patience
        self.car_max_patience = car_max_patience

        self.gas_station = simpy.PriorityResource(
            env, capacity=1) # 加油站大门
        self.fuel_pump = simpy.Container(
            env, 
            init=fuel_pump_capacity, # 默认初始是满的
            capacity=fuel_pump_capacity)
        self.refuel_machine = simpy.PriorityResource(
            env, capacity=num_refuel_machine)

        """ 在 __init__ 的时候直接创建进程
            
            此处包含以下进程（按父子进程顺序排列）：

            - car_source --> car
            - gas_station_control --> tank_truck
        """ 
        self.env.process(
            self.car_source(interval)
        )
        self.env.process(
            self.gas_station_control(threshold)
        )
        self.env.process(
            self.record_gas_station_status_info()
        )

    def car_source(self, interval):
        i = 1
        while True:
            yield self.env.timeout(car_arrive_time(interval))
            self.env.process(
                self.car(
                    'Car %d' % i,
                    self.car_min_patience,
                    self.car_max_patience
                    )
            )
            i = i + 1
            
    def car(self, name, 
            min_patience, 
            max_patience):
        """ 汽车进程
        """

        arrival_time = self.env.now

        fuel_tank_level = random.randint(*FUEL_TANK_LEVEL)
        patience = random.uniform(min_patience, max_patience)
        print(
            '%s arriving at gas station at %.1f' % (
                name, self.env.now
            )
        )

        with self.gas_station.request(
            priority=1 # 小汽车默认优先度 priority=1
            ) as req_1:
            # 请求进入加油站大门

            result = yield req_1 | self.env.timeout(patience)

            if req_1 in result:

                with self.refuel_machine.request() as req_2:
                    # 请求一个加油机
                    yield req_2

                    wait_time = env.now - arrival_time

                    self.wait_time_list[0].append(name)
                    self.wait_time_list[1].append(
                        wait_time
                    )
                    self.wait_time_list[2].append(
                        False
                    )

                    print( 
                        "%s arrived at the refuel machine at time %.1f" % (
                            name, self.env.now
                        )
                    )

                    # 计算油量需求
                    liters_required = FUEL_TANK_SIZE - fuel_tank_level
        
                    # 汽车加油时间
                    yield self.env.timeout(
                        liters_required / REFUELLING_SPEED
                        )

                    # 取油
                    yield self.fuel_pump.get(liters_required)

                    print( # 打印一下
                        '%s finished refuelling in %.1f seconds.' % (
                            name, self.env.now - arrival_time
                        )
                    )

            else:
                self.wait_time_list[0].append(name)
                self.wait_time_list[1].append(
                    patience
                )
                self.wait_time_list[2].append(
                    True
                )

                print(
                    '%s RENEGED' % (name)
                )

    def gas_station_control(
            self,
            threhold
            ):
        """ 定期检查燃油泵的水平
            如果水平低于阈值，就打电话给油罐车。

            备注：

            修改了原始代码，删除了 fuel.pump
            现在封装到类里面，仿真仓库 fuel.pump
            可以直接通过类属性的形式调用
            无需传入参数

            - refuelling_time 燃油车给加油站重新加油的时间
        """
        
        while True: # 一直无限循环，检查
            if (
                self.fuel_pump.level / (
                self.fuel_pump.capacity)
                ) * 100 < threhold:
                # fuel_pump.level 属性 
                # 返回的是仓库对象 fuel_pump 的当前油量
                # fuel_pump.capacity 属性 
                # 返回的是仓库对象 fuel_pump 的总容量

                # 我们现在需要叫油罐车来
                print('Calling tank truck at %d' % self.env.now)
                # 进入 tank_truck() 进程
                # 代表油罐车在给加油站的大油罐子供油
                yield self.env.process(
                    self.tank_truck()
                    )
                
            yield self.env.timeout(1)  
            # 每 10 秒检查一下

    def record_gas_station_status_info(self):
            
        while True:

            self.gas_station_status_list[0].append(
                self.env.now
                )
            self.gas_station_status_list[1].append(
                len(self.refuel_machine.queue)
                )
            self.gas_station_status_list[2].append(
                len(self.gas_station.queue)
            )
            self.gas_station_status_list[3].append(
                self.fuel_pump.level
            )
            yield self.env.timeout(1)

    def tank_truck(self):
        """ 在一段时间确定的延迟之后，抵达加油站，
            然后给大油罐子灌油。
            
        """
        yield self.env.timeout(TANK_TRUCK_TIME)
        print('Tank truck arriving at time %d' % self.env.now)
        with self.gas_station.request(priority=0) as req:
            # 加油车默认优先度 priority=0 优先级最高
            yield req
            while len(self.refuel_machine.queue) != 0:
                print(
                    "There is still car refuelling at time",
                    self.env.now)
                print("The tank truck is waiting at the gate...")
                yield self.env.timeout(1) # 每一秒检查一下
            print("There is no car refuelling now...")
            ammount = self.fuel_pump.capacity - self.fuel_pump.level
            # yield self.env.timeout(
            #         ammount / self.gas_station_refuelling_speed)
            # yield self.fuel_pump.put(ammount)
            for i in range(1, int(ammount)):
                yield self.fuel_pump.put(1)
                yield self.env.timeout(1 / self.gas_station_refuelling_speed)
            print('Tank truck refuelling %.1f liters.' % ammount)

    def result(self):
        """ 定义一个输出结果的方法

            返回值：

            - queue_info 类，也就是“排队结果信息类”，在下面定义
        """
        wait_time_df = pd.DataFrame([])
        wait_time_df['Customer'] = self.wait_time_list[0]
        wait_time_df['wait_time'] = self.wait_time_list[1]
        wait_time_df['is_reneged'] = self.wait_time_list[2]

        queue_len_df = pd.DataFrame(
                np.array(self.gas_station_status_list).T,
                columns=[
                    'time', 
                    'queue_len_gate',
                    'queue_len_machine',
                    'pump_level'
                    ]
                )
        return queue_info(
            self.env, 
            wait_time_df, 
            queue_len_df
            )

#             A____A
#            /*    *\
#           {   o  o }
#           A` >  3 /<
#          /        |
#         / !!!!! !!}
#        / ! \!!!!! |
#   ____{   ) |  |  |
#  / ___{!!!c |  |  |
# { (___ \__\__@@_)@_)
#  \____)

# In[3]:
# 定义排队结果信息类
class queue_info:
    """ 排队结果信息类

        包含如下的方法：

        - __init__ 初始化类
        - info 返回两张表：各个顾客的等待时间以及是否 reneged，
               以及各个时刻的队长
        - print_info 打印两张表的排队信息
        - plot_info 把排队参数画成图
    """
    def __init__(self, env, wait_time_df, queue_len_df):
        """ 类的初始化
        """
        self.env = env
        self.wait_time_df = wait_time_df
        self.queue_len_df = queue_len_df

    def info(self):
        """ 排队系统的参数信息
        """
        return self.wait_time_df, self.queue_len_df

    def print_info(self):
        """ 打印排队信息表
        """
        print(self.wait_time_df)
        print(self.queue_len_df)

    def plot_info(self, figsize=(8, 4), dpi=100):
        """ 画个图

            该方法将会绘制两张子图：

            1. 各个顾客的排队时间（用红色标记出 reneged）
            2. 各个时间点上的队列长度
        """
        fig, axes = plt.subplots(
                2, 1,
                figsize=figsize,
                dpi=dpi)
        
        colors = []
        for x in self.wait_time_df['is_reneged']:
            if x:
                colors.append('red')
            else:
                colors.append('C0')

        axes[0].bar(
            self.wait_time_df['Customer'],
            self.wait_time_df['wait_time'],
            color=colors)
        
        axes[0].set_title('wait time')
        axes[0].set_xticklabels([])

        axes[1].plot(
            self.queue_len_df['time'],
            self.queue_len_df['queue_len_gate'],
            c='C0',
            label='queue length - gate'
            )
        axes[1].plot(
            self.queue_len_df['time'],
            self.queue_len_df['queue_len_machine'],
            c='C1',
            label='queue length - refuel machine'
            )
        
        axes[1].set_title('queue length')
        fig.suptitle('Queue Result')
        fig.tight_layout()
        return fig, axes

# In[4]:
env = simpy.Environment()
simGasStation = GasStationSimulator(
    env,                          # 类自身及环境
    FUEL_PUMP_CAPACITY,           # 加油站的大油罐子容量
    NUM_REFUEL_MACHINE,           # 加油机的数量
    INTERVAL,                     # 汽车到达时间的负指数分布的参数
    GAS_STATION_REFUELLING_SPEED, # 加油车给加油站上油的速度
    THRESHOLD,                    # 呼叫加油车的阈值
    CAR_MIN_PATIENCE,
    CAT_MAX_PATIENCE
)
env.run(until=SIM_TIME)

# In[5]:
# 还没有写 queue_info 类
# 暂时拿这段代码块代替一下进行简单的可视化
fig, ax = plt.subplots(dpi=300)
fig.suptitle('加油站油罐油量变化情况')
ax.plot(
    simGasStation.gas_station_status_list[3],
    label='油量')
ax.set_xlabel('时间（秒）')
ax.set_ylabel('油量（升）')
ax.legend()

# In[6]:
result = simGasStation.result()
result.plot_info(dpi=300)

""" 代码运行输出结果展示和说明

典型的代码运行的输出结果：

Car 1 arriving at gas station at 90.8
Car 1 arrived at the refuel machine at time 90.8
Car 1 finished refuelling in 22.0 seconds.
Car 2 arriving at gas station at 319.2
Car 2 arrived at the refuel machine at time 319.2
Car 2 finished refuelling in 18.5 seconds.
Car 3 arriving at gas station at 349.2
Car 3 arrived at the refuel machine at time 349.2
Car 3 finished refuelling in 22.0 seconds.
Car 4 arriving at gas station at 423.0
Car 4 arrived at the refuel machine at time 423.0
Car 4 finished refuelling in 17.5 seconds.
Car 5 arriving at gas station at 550.5
Car 5 arrived at the refuel machine at time 550.5
Car 5 finished refuelling in 20.5 seconds.
Car 6 arriving at gas station at 580.5
Car 6 arrived at the refuel machine at time 580.5
Car 6 finished refuelling in 24.0 seconds.
Car 7 arriving at gas station at 692.7
Car 7 arrived at the refuel machine at time 692.7
Car 7 finished refuelling in 20.0 seconds.
Car 8 arriving at gas station at 808.2
Car 8 arrived at the refuel machine at time 808.2
Car 8 finished refuelling in 19.5 seconds.
Car 9 arriving at gas station at 877.2
...
Car 980 arrived at the refuel machine at time 99973.5
Car 992 arriving at gas station at 99993.6
Car 980 finished refuelling in 1247.1 seconds.
Car 981 arrived at the refuel machine at time 99994.0

下面截取一段，展示了油罐车进入加油站把其他车堵在外面的情形：

Car 137 arriving at gas station at 14581.2
Car 137 arrived at the refuel machine at time 14581.2
Car 137 finished refuelling in 19.0 seconds.
Tank truck arriving at time 14610
There is no car refuelling now...
Car 138 arriving at gas station at 14630.1
Car 139 arriving at gas station at 14919.5
Car 140 arriving at gas station at 14949.5
Car 141 arriving at gas station at 15001.8
Car 142 arriving at gas station at 15093.3
Car 143 arriving at gas station at 15123.3
Car 144 arriving at gas station at 15153.3
Car 145 arriving at gas station at 15191.6
Car 146 arriving at gas station at 15330.3
Car 147 arriving at gas station at 15360.3
Car 148 arriving at gas station at 15458.2
Car 149 arriving at gas station at 15578.4
Car 150 arriving at gas station at 15614.6
Car 151 arriving at gas station at 15644.6
Car 152 arriving at gas station at 15674.6
Car 153 arriving at gas station at 15899.1
Car 154 arriving at gas station at 15929.1
Car 155 arriving at gas station at 15959.1
Tank truck refuelling 5596.0 liters.
Car 138 arrived at the refuel machine at time 16009.0
Car 138 finished refuelling in 1403.4 seconds.
Car 139 arrived at the refuel machine at time 16033.5
Car 139 finished refuelling in 1130.0 seconds.
Car 140 arrived at the refuel machine at time 16049.5

"""

# %%
